package file

import (
	"context"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/gtime"
	"path/filepath"
	"server/internal/dao"
	"server/internal/library/upload"
	"server/internal/model"
	"server/internal/model/do"
	"server/internal/model/entity"
	"server/internal/service"
	"server/internal/tool"
	"strconv"
	"strings"
)

type sFile struct {
}

func init() {
	service.RegisterFile(&sFile{})
}

// GetFileUploadConfig 文件上传相关配置
func (s *sFile) GetFileUploadConfig(ctx context.Context) (data model.CoreConfigFileUpload, err error) {
	data = model.CoreConfigFileUpload{}
	result, err := g.Cfg().Get(ctx, "fileUpload")
	if err != nil {
		return
	}
	err = result.Scan(&data)
	return
}

// ValidateExtension 验证扩展名
func (s *sFile) ValidateExtension(allowExtension string, ext string) bool {
	if len(allowExtension) == 0 {
		return false
	}
	allowExtensions := strings.Split(allowExtension, ",")
	for _, item := range allowExtensions {
		if item == ext {
			return true
		}
	}
	return false
}

// GenerateUploadFileTempPath 生成一个本地上传的文件临时路径
func (s *sFile) GenerateUploadFileTempPath(ctx context.Context, subject string) (tempPath string, err error) {
	config, err := s.GetFileUploadConfig(ctx)
	if err != nil {
		return "", err
	}

	year := strconv.Itoa(gtime.Now().Year())
	month := strconv.Itoa(gtime.Now().Month())
	day := strconv.Itoa(gtime.Now().Day())
	tempPath = filepath.Join(config.Local.FileServerDIR, subject, year, month, day)

	return filepath.ToSlash(tempPath), nil
}

// NewFile 新建一个空文件
func (s *sFile) NewFile(ctx context.Context, subject string, filename string) (uploadFile model.FileUploadOutput, err error) {
	// 创建待生成的文件信息
	absolutePath, err := s.GenerateUploadFileTempPath(ctx, subject)
	if err != nil {
		return
	}

	// 文件路径+文件名称 ToSlash方法将反斜杠转为正斜跨，兼容wins场景
	absolutePath = filepath.ToSlash(filepath.Join(absolutePath, filename))

	// 创建文件
	err = tool.NewFile(uploadFile.AbsolutePath)

	return uploadFile, err
}

// ValidateUploadFileIsAllow 验证文件是否允许上传
func (s *sFile) ValidateUploadFileIsAllow(ctx context.Context, f *ghttp.UploadFile) error {
	config, err := s.GetFileUploadConfig(ctx)
	if err != nil {
		return err
	}
	if len(f.Filename) == 0 {
		return gerror.New("文件名异常")
	}

	//验证文件扩展名
	ext := strings.ToLower(strings.Trim(filepath.Ext(f.Filename), "."))
	if !s.ValidateExtension(config.Common.FileServerUploadAllowExtensions, ext) {
		return gerror.Newf("文件类型[%s]不允许上传", ext)
	}

	//验证文件大小
	maxSize := config.Common.FileServerUploadMaxSize * 1024 * 1024 //MB转换B
	if f.Size > maxSize {
		return gerror.Newf("文件大小已超过上限[%dMB]", config.Common.FileServerUploadMaxSize)
	}

	return nil
}

// FileUpload 单文件上传-支持动态选择文件上传的类型（阿里云oss、七牛云oss、本地）
func (s *sFile) FileUpload(ctx context.Context, f *ghttp.UploadFile, storageType int, fileSubject string, categoryID int64) (out model.FileUploadOutput, err error) {
	out = model.FileUploadOutput{}
	if err = s.ValidateUploadFileIsAllow(ctx, f); err != nil {
		return
	}
	// 创建一个文件上传客户端
	uploadClient := upload.NewOssClient(storageType)
	if uploadClient == nil {
		err = gerror.New("创建文件驱动失败")
		return
	}
	// 文件上传
	out, err = uploadClient.UploadFile(ctx, f, fileSubject)

	if err != nil {
		return
	}
	// 文件上传记录存储
	_, err = dao.SystemFile.Ctx(ctx).Data(do.SystemFile{
		CategoryId:  categoryID,
		StorageType: storageType,
		Name:        f.Filename,
		ContentType: f.FileHeader.Header.Get("Content-Type"),
		Extension:   strings.Trim(filepath.Ext(f.Filename), "."),
		Size:        f.Size,
		Path:        out.RelativePath,
		CreatedBy:   service.SystemLogin().GetAdminID(ctx),
	}).InsertAndGetId()
	return
}

// FileDelete 文件删除
func (s *sFile) FileDelete(ctx context.Context, key string) (err error) {
	//查询已上传的文件
	fileInfo := new(entity.SystemFile)
	err = dao.SystemFile.Ctx(ctx).Where(do.SystemFile{Path: key}).Scan(fileInfo)
	if err != nil {
		return
	}
	// 创建一个文件上传客户端
	uploadClient := upload.NewOssClient(fileInfo.StorageType)
	if uploadClient == nil {
		err = gerror.New("创建文件驱动失败")
		return
	}
	// 调用驱动进行文件删除
	err = uploadClient.DeleteFile(ctx, fileInfo.Path)

	if err != nil {
		return
	}

	//标记数据库中文件已删除
	_, err = dao.SystemFile.Ctx(ctx).Data(do.SystemFile{
		DeletedBy: service.SystemLogin().GetAdminID(ctx),
		DeletedAt: gtime.Now(),
	}).Where(do.SystemFile{Id: fileInfo.Id}).Update()

	return
}

// GetAttachmentList 获取附件列表
func (s *sFile) GetAttachmentList(ctx context.Context, in model.FileListInput) (list interface{}, total int, err error) {
	where := g.Map{}
	if len(in.OriginalName) > 0 {
		where["original_name"] = in.OriginalName
	}
	if len(in.FileCategoryIds) > 0 {
		where["category_id"] = in.FileCategoryIds
	}

	attachments := make([]model.FileListOutput, 0)
	err = dao.SystemFile.Ctx(ctx).Where(where).Page(in.Page, in.PageSize).OrderDesc("id").ScanAndCount(&attachments, &total, true)

	for index, item := range attachments {
		attachments[index].FileURL = upload.FormatUploadFileURL(ctx, item.Path, item.StorageType)
	}

	return attachments, total, err
}

// AddAttachmentCategory 添加附件分类
func (s *sFile) AddAttachmentCategory(ctx context.Context, in model.FileCategoryAddInput) (lastId int64, err error) {
	total, err := dao.SystemFileCategory.Ctx(ctx).Count(do.SystemFileCategory{Name: in.Name})
	if err != nil {
		return 0, err
	}
	if total > 0 {
		return 0, gerror.New("分类名称已存在")
	}

	return dao.SystemFileCategory.Ctx(ctx).Data(do.SystemFileCategory{
		ParentId: in.ParentId,
		Name:     in.Name,
	}).InsertAndGetId()
}

// EditAttachmentCategory 修改附件分类
func (s *sFile) EditAttachmentCategory(ctx context.Context, id int, in model.FileCategoryEditInput) (rowsAffected int64, err error) {
	total, err := dao.SystemFileCategory.Ctx(ctx).
		WhereNot("id", id).
		Where(do.SystemFileCategory{Name: in.Name}).
		Count()
	if err != nil {
		return 0, err
	}
	if total > 0 {
		return 0, gerror.New("分类名称已存在")
	}

	return dao.SystemFileCategory.Ctx(ctx).Data(do.SystemFileCategory{
		ParentId: in.ParentId,
		Name:     in.Name,
	}).Where(do.SystemFileCategory{Id: id}).UpdateAndGetAffected()
}

// DelAttachmentCategory 删除附件分类
func (s *sFile) DelAttachmentCategory(ctx context.Context, id int) (rowsAffected int64, err error) {
	result, err := dao.SystemFileCategory.Ctx(ctx).Where(do.SystemFileCategory{Id: id}).Delete()
	if err != nil {
		return 0, err
	}
	return result.RowsAffected()
}

// GetAttachmentCategoryTree 获取附件分类树形结构数据
func (s *sFile) GetAttachmentCategoryTree(ctx context.Context) (tree []model.FileCategoryTreeNode, err error) {
	items := make([]model.FileCategoryTreeNode, 0)
	err = dao.SystemFileCategory.Ctx(ctx).Scan(&items)
	if err != nil {
		return
	}
	return s.attachmentCategoryListToTree(items, 0), nil
}

// attachmentCategoryListToTree 列表数据转树形结构数据
func (s *sFile) attachmentCategoryListToTree(nodes []model.FileCategoryTreeNode, pid int) []model.FileCategoryTreeNode {
	tree := make([]model.FileCategoryTreeNode, 0)
	for _, node := range nodes {
		if node.ParentId == pid {
			node.Children = s.attachmentCategoryListToTree(nodes, node.Id)
			tree = append(tree, node)
		}
	}
	return tree
}
